#include "CtrolPointCamera.h"
#include "CGlobals.h"
#include "CLevelEditor.h"
#include "CUserInterface.h"
#include "DoUndoCommands.h"
#include "CSceneObject.h"
#include "irrlicht.h"
#include <cmath>

extern CGlobals* g;
extern CLevelEditor* editor;
extern CUserInterface* ui;

const float k_cameraDefaultHeight = 120.0f;

///////////////////////////////////////////////////////////////////////////////////

ControlPointCamera::ControlPointCamera ()
	:m_camCtrlPointObj(0),	 
	 m_hasPlane(false),
	 m_leftMousePressed(false),
	 m_rightMousePressed(false)
{		
}

void ControlPointCamera::setAttribute (const c8* attribute, float value)
{
	if (m_camCtrlPointObj!=0)
	{
		m_camCtrlPointObj->m_UserData->setAttribute(attribute, value);		
	}
}

float ControlPointCamera::getAttribute (const c8* attribute)
{
	if (m_camCtrlPointObj!=0)
	{
		return m_camCtrlPointObj->m_UserData->getAttributeAsFloat(attribute);
	}
	return 0.0f;
}

bool ControlPointCamera::OnEvent (const SEvent& event)
{
	const float k_cam_speed_normal = 3.0f;
	const float k_cam_speed_quick = 20.0f;

	switch (event.EventType)
	{
		case EET_KEY_INPUT_EVENT:
			if (event.KeyInput.PressedDown)
			{
				float cam_speed = k_cam_speed_normal;
				if(event.KeyInput.Shift)
					cam_speed = k_cam_speed_quick;

				switch (event.KeyInput.Key)
				{
					case KEY_KEY_W:													
						m_targetOffset += m_camDir*cam_speed;	
						//correctTarget();
						break;
					case KEY_KEY_S:													
						m_targetOffset -= m_camDir*cam_speed;
						//correctTarget();
						break;
					case KEY_KEY_A:
					{
						irr::core::vector3df up = m_camera->getUpVector();
						irr::core::vector3df right = -m_camDir.crossProduct(up);						
						m_targetOffset -= right*cam_speed;
						//correctTarget();
						break;
					}
					case KEY_KEY_D:
					{
						irr::core::vector3df up = m_camera->getUpVector();
						irr::core::vector3df right = -m_camDir.crossProduct(up);						
						m_targetOffset += right*cam_speed;
						//correctTarget();
						break;
					}
					case KEY_LEFT:					
						m_yaw+=cam_speed;
						break;					

					case KEY_RIGHT:					
						m_yaw-=cam_speed;
						break;					

					case KEY_UP:					
						m_pitch+=cam_speed;
						break;					

					case KEY_DOWN:					
						m_pitch-=cam_speed;
						break;					

					case KEY_HOME:					
						m_camDis-=cam_speed*2;
						break;					

					case KEY_END:					
						m_camDis+=cam_speed*2;
						break;					

					case KEY_PRIOR:					
						m_camHeight-=cam_speed*2;
						break;					

					case KEY_NEXT:					
						m_camHeight+=cam_speed*2;
						break;	
				}

				updateCamera(m_camera);
			}
			break;

		case EET_MOUSE_INPUT_EVENT:
			switch (event.MouseInput.Event)
			{
				case EMIE_LMOUSE_PRESSED_DOWN:
					m_leftMousePressed = true;
					m_mouseStartPos = irr::core::position2di(event.MouseInput.X, event.MouseInput.Y);
					break;
				case EMIE_LMOUSE_LEFT_UP:
					m_leftMousePressed = false;
					break;
				case EMIE_RMOUSE_PRESSED_DOWN:
					m_rightMousePressed = true;
					break;
				case EMIE_RMOUSE_LEFT_UP:
					m_rightMousePressed = false;
					break;
				case EMIE_MOUSE_MOVED:
					if (m_leftMousePressed)
					{
						position2di mouse = position2di(event.MouseInput.X, event.MouseInput.Y);
						position2di diff = mouse - m_mouseStartPos;
						m_yaw-=0.15f*diff.X;
						m_pitch-=0.15f*diff.Y;						
					}					
					else if (m_rightMousePressed)
					{
						position2di mouse = position2di(event.MouseInput.X, event.MouseInput.Y);
						position2di diff = mouse - m_mouseStartPos;						
						m_targetHeightOff+=diff.Y;
						
					}
					m_mouseStartPos = irr::core::position2di(event.MouseInput.X, event.MouseInput.Y);
					break;
				case EMIE_MOUSE_WHEEL:
					irr::f32 dwheel = event.MouseInput.Wheel;
					m_camDis-=dwheel*100;
					break;
			}

			updateCamera(m_camera);
			break;
	}

	// if nobody processed the event, return false
	return false;
}

void ControlPointCamera::setCamCtrlPointObject(CSceneObject* object, ICameraSceneNode* pCamera)
{
	m_camCtrlPointObj = object;
	m_camera = pCamera;
	m_ctrlPointPos = m_camCtrlPointObj->m_SceneNode->getAbsolutePosition();
	m_targetOffset.set(getAttribute("targetOffsetX"),getAttribute("targetOffsetY"),getAttribute("targetOffsetZ"));
	m_camDir.set(getAttribute("dx"),getAttribute("dy"),getAttribute("dz"));
	m_camDir.normalize();
	m_camDis = getAttribute("dis");
	m_targetHeightOff = getAttribute("targetHeightOff");
	m_camHeight = k_cameraDefaultHeight;


	//convert lookat to euler angles
	irr::core::matrix4 matRot;
	irr::core::vector3df up = m_camera->getUpVector();
	irr::core::vector3df right = -m_camDir.crossProduct(up);
	right.normalize();
	up = -right.crossProduct(m_camDir);
	up.normalize();
	matRot[0] = right.X;
	matRot[1] = right.Y;
	matRot[2] = right.Z;
	matRot[4] = m_camDir.X;
	matRot[5] = m_camDir.Y;
	matRot[6] = m_camDir.Z;
	matRot[8] = up.X;
	matRot[9] = up.Y;
	matRot[10] = up.Z;

	irr::core::vector3df rotVec = matRot.getRotationDegrees();
	m_yaw = rotVec.Z;
	m_pitch = rotVec.X;
	m_roll = rotVec.Y;
	

	computePlane();	

	m_leftMousePressed = false;
	m_rightMousePressed = false;
}

void ControlPointCamera::resetCameraFromObjectData()
{
	assert(m_camCtrlPointObj!=0);		

	setCamCtrlPointObject(m_camCtrlPointObj, m_camera);
}

void ControlPointCamera::updateCamera(ICameraSceneNode* pCameraNode)
{	
	//compute dir from euler
	irr::core::matrix4 matRot;
	matRot.setRotationDegrees(irr::core::vector3df(m_pitch, m_roll, m_yaw));
	m_camDir.set(matRot[4],matRot[5],matRot[6]);
	m_camDir.normalize();

	irr::core::vector3df camTarget = m_ctrlPointPos+m_targetOffset;
	camTarget.Z+=m_camHeight;
	camTarget.Z+=m_targetHeightOff;
	irr::core::vector3df camPos = camTarget-m_camDir*m_camDis;

	pCameraNode->setPosition(camPos);
	pCameraNode->setTarget(camTarget);
	pCameraNode->updateAbsolutePosition();
}

void ControlPointCamera::saveToCamCtrlPointObject()
{
	setAttribute("dx", m_camDir.X);
	setAttribute("dy", m_camDir.Y);
	setAttribute("dz", m_camDir.Z);
	setAttribute("dis", m_camDis);
	setAttribute("targetOffsetX",m_targetOffset.X);
	setAttribute("targetOffsetY",m_targetOffset.Y);
	setAttribute("targetOffsetZ",m_targetOffset.Z);
	setAttribute("targetHeightOff", m_targetHeightOff);

	ui->updateProperties();

	m_leftMousePressed = false;
	m_rightMousePressed = false;
}

void ControlPointCamera::computePlane()
{
	m_hasPlane = false;

	if(m_camCtrlPointObj==0)
		return;

	int caID = m_camCtrlPointObj->m_UserData->getAttributeAsInt("!^Owner^CameraArea");
	if(caID<0)
	{
		return;
	}

	CSceneObject* pObj = editor->getSceneObjectFromID(caID);
	if(pObj==0)
	{
		return;
	}
		
	//get 3 ctrl points, ignore 4th
	int id1 = pObj->m_UserData->getAttributeAsInt("^Point1^CamCtrlPoint");
	int id2 = pObj->m_UserData->getAttributeAsInt("^Point2^CamCtrlPoint");
	int id3 = pObj->m_UserData->getAttributeAsInt("^Point3^CamCtrlPoint");	

	if(id1>=0 && id2>=0 && id3>=0)
	{
		CSceneObject* obj1= editor->getSceneObjectFromID(id1);
		CSceneObject* obj2= editor->getSceneObjectFromID(id2);
		CSceneObject* obj3= editor->getSceneObjectFromID(id3);		

		if(obj1!=0 && obj2!=0 && obj3!=0)
		{
			
			//define the plane
			irr::core::vector3df pos1 = obj1->m_SceneNode->getAbsolutePosition();
			irr::core::vector3df pos2 = obj2->m_SceneNode->getAbsolutePosition();
			irr::core::vector3df pos3 = obj3->m_SceneNode->getAbsolutePosition();

			m_areaBasePlane.setPlane(pos1,pos2,pos3);
			m_hasPlane = true;
		}
	}	
}

void ControlPointCamera::correctTarget()
{
	if(!m_hasPlane || m_camCtrlPointObj==0)
		return;

	irr::core::vector3df limitPos = m_ctrlPointPos + m_targetOffset;					

	if(m_areaBasePlane.classifyPointRelation(limitPos)!=ISREL3D_PLANAR)
	{
		irr::core::vector3df newPos;
		if(!m_areaBasePlane.getIntersectionWithLine(limitPos,m_areaBasePlane.Normal,newPos))
		{
			return;//should never occur							
		}

		m_targetOffset = newPos - m_ctrlPointPos;		
	}				
}

//////////////////////////////////////////////////////////////////////////////////////

CCtrolPointCameraAnimator::CCtrolPointCameraAnimator (ICameraSceneNode* camera)
	:m_camera(camera),
	 m_currentControlPointCameraIdx(-1)	 
{
	m_controlPointCameras.clear();
	m_controlPointCameras.reserve(4);
}

bool CCtrolPointCameraAnimator::isEventReceiverEnabled () const
{
	return true;
}

bool CCtrolPointCameraAnimator::OnEvent (const SEvent& event)
{
	//swtich control point cameras by +/-
	if(event.EventType==EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown)
	{						
		switch (event.KeyInput.Key)
		{
			case KEY_PLUS:													
				m_currentControlPointCameraIdx ++;	
				m_currentControlPointCameraIdx%=m_controlPointCameras.size();
				break;
			case KEY_MINUS:													
				m_currentControlPointCameraIdx --;	
				if(m_currentControlPointCameraIdx<0)
					m_currentControlPointCameraIdx=m_controlPointCameras.size()-1;
				break;
			
		}	
	}

	ControlPointCamera* pCPCamera = m_controlPointCameras.at(m_currentControlPointCameraIdx);
	return pCPCamera->OnEvent(event);
}

void CCtrolPointCameraAnimator::animateNode (ISceneNode* node, u32 timeMs)
{
	if(m_currentControlPointCameraIdx>=0)
	{
		ControlPointCamera* pCPCamera = m_controlPointCameras.at(m_currentControlPointCameraIdx);
		pCPCamera->updateCamera(m_camera);
	}	
}

void CCtrolPointCameraAnimator::addControlPointCamera(CSceneObject* ccpObj)
{
	ControlPointCamera* pCPCamera = new ControlPointCamera();
	pCPCamera->setCamCtrlPointObject(ccpObj, m_camera);
	m_controlPointCameras.push_back(pCPCamera);	
}

void CCtrolPointCameraAnimator::initControlPointCameras(CSceneObject* ccpObjSelected)
{
	freeControlPointCameras();//safe guard

	if(ccpObjSelected==0)
		return;

	int caID = ccpObjSelected->m_UserData->getAttributeAsInt("!^Owner^CameraArea");
	if(caID<0)
	{		
		//single point
		m_currentControlPointCameraIdx = 0;
		addControlPointCamera(ccpObjSelected);
		return;
	}

	CSceneObject* pObj = editor->getSceneObjectFromID(caID);
	if(pObj==0)
	{
		//set as single point
		m_currentControlPointCameraIdx = 0;
		addControlPointCamera(ccpObjSelected);
		return;
	}
	
	char _tmp_buf[256];
	for(int i=0; i<4; ++i)
	{
		sprintf_s(_tmp_buf,"^Point%d^CamCtrlPoint",(i+1));
		int ccpID = pObj->m_UserData->getAttributeAsInt(_tmp_buf);
		if(ccpID>=0)
		{
			CSceneObject* ccpObj= editor->getSceneObjectFromID(ccpID);
			if(ccpObj==ccpObjSelected)
				m_currentControlPointCameraIdx = i;
			addControlPointCamera(ccpObj);
		}
	}

	if(m_currentControlPointCameraIdx>=0)
	{
		ControlPointCamera* pCPCamera = m_controlPointCameras.at(m_currentControlPointCameraIdx);
		pCPCamera->updateCamera(m_camera);
	}	
}

void CCtrolPointCameraAnimator::freeControlPointCameras()
{	
	for(unsigned int i=0; i<m_controlPointCameras.size(); ++i)
	{
		ControlPointCamera* pCPCamera = m_controlPointCameras.at(i);
		delete pCPCamera;
	}

	m_controlPointCameras.clear();

	m_currentControlPointCameraIdx = -1;
}

void CCtrolPointCameraAnimator::resetCurrentControlPointCamera()
{
	if(m_currentControlPointCameraIdx>=0)
	{
		ControlPointCamera* pCPCamera = m_controlPointCameras.at(m_currentControlPointCameraIdx);
		pCPCamera->resetCameraFromObjectData();
	}	
}

void CCtrolPointCameraAnimator::saveToCamCtrlPointObjects()
{
	for(unsigned int i=0; i<m_controlPointCameras.size(); ++i)
	{
		ControlPointCamera* pCPCamera = m_controlPointCameras.at(i);
		pCPCamera->saveToCamCtrlPointObject();
	}
}

irr::scene::ISceneNode* CCtrolPointCameraAnimator::getCurrentControlPointNode()
{
	if(m_currentControlPointCameraIdx>=0)
	{
		ControlPointCamera* pCPCamera = m_controlPointCameras.at(m_currentControlPointCameraIdx);
		return pCPCamera->getCtrlPointObj()->m_SceneNode;				
	}

	return 0;
}

